<template>
    <MyArticle title="基于cesium.js的web端项目加载shpfile大文件数据" date="2025年07月30日">
        <div class="_article-content">
            <h3 class="title">一、转成geojson文件后使用Primitive方式渲染</h3>
            <p>QGIS软件可以将shpfile文件转为geojson格式。将shpfile文件拖动到QGIS软件中，右键图层，在弹出菜单中选择“导出”，再选择“要素另存为”：</p>
            <figure class="common">
                <a class="pswp-img" href="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/01.webp" data-pswp-width="1911" data-pswp-height="1026" target="_blank">
                    <img class="thumbnail-img" src="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/01.webp"/>
                </a>
            </figure>
            <p>在弹出的窗口中设置好导出文件位置，CRS选“EPSG:4326-WGS84”，为减少导出json文件大小，可以将不需要的属性字段勾去，并且将经纬度数据精度（小数位数）调低：</p>
            <figure class="common">
                <a class="pswp-img" href="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/02.webp" data-pswp-width="593" data-pswp-height="664" target="_blank">
                    <img class="thumbnail-img" src="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/02.webp"/>
                </a>
            </figure>
            <p>如果导出之后的json文件比较大，则需要想办法压缩json文件：</p>
            <p>1、去除json文件里面的空格、无用的属性、经纬度数据的第三位数据：</p>
            <figure class="common">
                <a class="pswp-img" href="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/03.webp" data-pswp-width="1722" data-pswp-height="930" target="_blank">
                    <img class="thumbnail-img" src="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/03.webp"/>
                </a>
            </figure>
            <p>2、开启gzip压缩，构建时生成gz文件，vite.config中plugins如下配置：</p>
            <pre><code>plugins: [
    vue(),
    compressPlugin({
        ext: '.gz',
        algorithm: 'gzip',
        threshold: 10240,
        deleteOriginFile: false
    })
]</code></pre>
            <p>nginx配置如下：</p>
            <pre><code>gzip on;
gzip_static on;
gzip_comp_level 4;
gzip_types text/plain text/html text/css application/x-javascript text/xml application/xml application/xml application/json;</code></pre>
            <p>在cesium中显示polygon可以使用entity绘制，但在数据量大的情况下则不可取，会非常卡顿，需改用Primitive方式绘制：</p>
            <pre><code>const addFeatures = (features) => {
    const color = 'rgba(26,89,7,0.8)' // 设置多边形颜色
    const instances = []
    let polygonArr = null
    let polygon = null
    let geometry = null
    let feature = null
    let coordinates = null
    let j = 0
    for (let i = 0; i < features.length; i++) {
        feature = features[i]
        for(j = 0; j < feature.geometry.coordinates.length; j++) {
            coordinates = feature.geometry.coordinates[j]
            coordinates.forEach(item => {
                polygonArr = item.flat()
                polygon = new Cesium.PolygonGeometry({
                    polygonHierarchy: new Cesium.PolygonHierarchy(Cesium.Cartesian3.fromDegreesArray(polygonArr)),
                    vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
                })
                geometry = Cesium.PolygonGeometry.createGeometry(polygon)
                instances.push(new Cesium.GeometryInstance({
                    geometry: geometry,
                    attributes: {
                        color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromCssColorString(color))
                    }
                }))
            })
        }
    }
    const primitive = new Cesium.Primitive({
        geometryInstances: instances,
        show: true,
        appearance: new Cesium.PerInstanceColorAppearance({ translucent: true, closed: false }),
        asynchronous: false
    })
    viewer.scene.primitives.add(primitive)
}

// 加载geojson文件
const res = await fetch('export_data.geojson').then(response => response.json())
addFeatures(res.features, entitiesArr[type].color, type)</code></pre>
            <h3 class="title">二、利用GeoServer将矢量地图数据发布为TMS瓦片服务</h3>
            <p>对于更大的geojson文件（几十M或者上百M），以上方式依然存在加载慢、渲染慢等性能问题。<a href="https://geoserver.org/?lang=zh-cn" target="_blank">GeoServer</a>是开源软件，可以将shp文件转成栅格瓦片服务，在cesium中添加瓦片图层即可实现相同的渲染效果。</p>
            <p>GeoServer依赖jdk8/jdk11/jdk17环境，需要先安装这三个版本之一的jdk。</p>
            <h3 class="sub-title">2.1 在centos上安装GeoServer</h3>
            <p>下载并解压最新版本的安装包：</p>
            <pre><code>cd /opt/geoserver
wget https://jaist.dl.sourceforge.net/project/geoserver/GeoServer/2.27.2/geoserver-2.27.2-bin.zip?viasf=1
unzip geoserver-2.27.2-bin.zip
sudo chown -R $USER:$USER /opt/geoserver // 设置Geoserver目录的所有者为当前用户</code></pre>
            <p>启动GeoServer：</p>
            <pre><code>cd /opt/geoserver/bin
./start.sh</code></pre>
            <p>启动成功后，Geoserver默认监听8080端口，通过以下地址访问服务：</p>
            <pre><code>http://localhost:8080/geoserver</code></pre>
            <p>默认账号：admin，密码：geoserver</p>
            <h3 class="sub-title">2.2 配置GeoServer为系统服务</h3>
            <p>创建systemd服务文件：</p>
            <pre><code>vi /etc/systemd/system/geoserver.service</code></pre>
            <p>输入以下内容，根据实际安装路径和用户进行调整：</p>
            <pre><code>[Unit]
Description=GeoServer Service
After=network.target

[Service]
Type=simple
User=root
ExecStart=/opt/geoserver/bin/startup.sh
ExecStop=/opt/geoserver/bin/shutdown.sh
WorkingDirectory=/opt/geoserver
Restart=on-abort

[Install]
WantedBy=multi-user.target
</code></pre>
            <p>保存之后设置脚本权限：</p>
            <pre><code>sudo chmod +x /opt/geoserver/bin/startup.sh
sudo chmod +x /opt/geoserver/bin/shutdown.sh</code></pre>
            <p>启用并启动服务：</p>
            <pre><code>sudo systemctl daemon-reload
sudo systemctl enable geoserver // 启用服务
sudo systemctl start geoserver // 启动服务
sudo systemctl status geoserver // 查看服务状态
// sudo systemctl restart geoserver // 重启服务
// sudo systemctl stop geoserver // 停止服务</code></pre>
            <h3 class="sub-title">2.3 shpfile文件构建TMS瓦片图层服务</h3>
            <p>登录geoserver，创建工作空间：</p>
            <figure class="common">
                <a class="pswp-img" href="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/05.webp" data-pswp-width="1572" data-pswp-height="967" target="_blank">
                    <img class="thumbnail-img" src="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/05.webp"/>
                </a>
            </figure>
            <p>输入空间名称，可设为默认，点击保存：</p>
            <figure class="common">
                <a class="pswp-img" href="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/04.webp" data-pswp-width="1109" data-pswp-height="537" target="_blank">
                    <img class="thumbnail-img" src="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/04.webp"/>
                </a>
            </figure>
            <p>创建存储空间：</p>
            <figure class="common">
                <a class="pswp-img" href="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/06.webp" data-pswp-width="1617" data-pswp-height="761" target="_blank">
                    <img class="thumbnail-img" src="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/06.webp"/>
                </a>
            </figure>
            <p>矢量数据源选择shapefile：</p>
            <figure class="common">
                <a class="pswp-img" href="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/07.webp" data-pswp-width="1706" data-pswp-height="889" target="_blank">
                    <img class="thumbnail-img" src="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/07.webp"/>
                </a>
            </figure>
            <p>填写数据源名称，选择shapefile文件位置，保存：</p>
            <figure class="common">
                <a class="pswp-img" href="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/08.webp" data-pswp-width="1920" data-pswp-height="945" target="_blank">
                    <img class="thumbnail-img" src="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/08.webp"/>
                </a>
            </figure>
            <p>在新建图层页面点击发布：</p>
            <figure class="common">
                <a class="pswp-img" href="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/09.webp" data-pswp-width="1920" data-pswp-height="945" target="_blank">
                    <img class="thumbnail-img" src="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/09.webp"/>
                </a>
            </figure>
            <p>进入编辑图层页面，(图层命名最好以英文命名)</p>
            <figure class="common">
                <a class="pswp-img" href="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/10.webp" data-pswp-width="1920" data-pswp-height="945" target="_blank">
                    <img class="thumbnail-img" src="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/10.webp"/>
                </a>
            </figure>
            <p>图层边框点击“从数据中计算”、“Compute from native bounds”，保存：</p>
            <figure class="common">
                <a class="pswp-img" href="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/11.webp" data-pswp-width="1920" data-pswp-height="945" target="_blank">
                    <img class="thumbnail-img" src="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/11.webp"/>
                </a>
            </figure>
            <p>图层tms列表地址可以从下面链接中找到：</p>
            <pre><code>http://localhost:8080/geoserver/gwc/service/tms/1.0.0</code></pre>
            <p>选择EPSG:900913的那个png格式的地址：</p>
            <figure class="common">
                <a class="pswp-img" href="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/12.webp" data-pswp-width="1888" data-pswp-height="994" target="_blank">
                    <img class="thumbnail-img" src="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/12.webp"/>
                </a>
            </figure>
            <p>使用leaflet预览该图层：</p>
            <pre><code>&lt;!DOCTYPE html&gt;
&lt;html lang="zh-cmn-Hans"&gt;
&lt;head&gt;
    &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
    &lt;meta name="viewport" content="initial-scale=1.0, user-scalable=no" /&gt;
    &lt;title&gt;预览GeoServer&lt;/title&gt;
    &lt;script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/leaflet/1.9.4/leaflet.min.js"/&gt;
    &lt;link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/leaflet/1.9.4/leaflet.min.css"/&gt;
    &lt;style&gt;
        html, body{
            margin:0px; padding:0px;width:100%; height:100%;
        }
        #map{
            width:100%;
            height:100%;
        }
    &lt;/style&gt;
    &lt;script&gt;
        function init () {
            const wrap = document.querySelector('#map')
            const maxZoom = 21
            const minZoom = 4
            const map = L.map(wrap, { scrollWheelZoom: true, positionControl: true, zoomControl: false })
            const bounds = L.latLngBounds([[23.84907894, 113.15847545], [30.49762609, 118.83103814]])
            const url = `http://localhost:8080/geoserver/gwc/service/tms/1.0.0/yrsz%3A%E6%B1%9F%E8%A5%BF%E7%9C%81_%E5%8E%BF%E7%95%8C@EPSG%3A900913@png/{z}/{x}/{y}.png?authkey=${authkey}`
            map.fitBounds(bounds)
            const layer = L.tileLayer(url, {
                bounds,
                minZoom,
                maxZoom,
                tms: true, // 是否反转Y轴的索引值
                opacity: 1,
                detectRetina: true
            }).addTo(map)
        }
    &lt;/script&gt;
&lt;/head&gt;
&lt;body onload="init()"&gt;
    &lt;div id="map"/&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
            <figure class="common">
                <a class="pswp-img" href="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/13.webp" data-pswp-width="1920" data-pswp-height="945" target="_blank">
                    <img class="thumbnail-img" src="https://gitee.com/bravelin/pics/raw/master/gis/geoserver/13.webp"/>
                </a>
            </figure>
            <p>设置样式：以#0AED5D填充、边线颜色为黑色</p>
            <pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;StyledLayerDescriptor version="1.0.0" 
 xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd" 
 xmlns="http://www.opengis.net/sld" 
 xmlns:ogc="http://www.opengis.net/ogc" 
 xmlns:xlink="http://www.w3.org/1999/xlink" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  &lt;NamedLayer&gt;
    &lt;Name&gt;polygonStyle&lt;/Name&gt;
    &lt;UserStyle&gt;
      &lt;Title>Ayly Polygon&lt;/Title&gt;
      &lt;Abstract>A sample style that draws a polygon&lt;/Abstract&gt;
      &lt;FeatureTypeStyle&gt;
        &lt;Rule&gt;
          &lt;Name&gt;rule1&lt;/Name&gt;
          &lt;Title&gt;Gray Polygon with Black Outline&lt;/Title&gt;
          &lt;Abstract&gt;A polygon with a gray fill and a 1 pixel black outline&lt;/Abstract&gt;
          &lt;PolygonSymbolizer&gt;
            &lt;Fill&gt;
              &lt;CssParameter name="fill"&gt;#0AED5D&lt;/CssParameter&gt;
              &lt;CssParameter name="fill-opacity"&gt;0.8&lt;/CssParameter&gt;
            &lt;/Fill&gt;
            &lt;Stroke&gt;
              &lt;CssParameter name="stroke"&gt;#000000&lt;/CssParameter&gt;
              &lt;CssParameter name="stroke-width"&gt;0.5&lt;/CssParameter&gt;
              &lt;CssParameter name="stroke-opacity"&gt;0.1&lt;/CssParameter&gt;
            &lt;/Stroke&gt;
          &lt;/PolygonSymbolizer&gt;
        &lt;/Rule&gt;
      &lt;/FeatureTypeStyle&gt;
    &lt;/UserStyle&gt;
  &lt;/NamedLayer&gt;
&lt;/StyledLayerDescriptor&gt;
</code></pre>
        </div>
    </MyArticle>
</template>